﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using JetBrains.Application.Progress;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.CodeInsight.Services.CodeCleanup;
using JetBrains.ReSharper.CodeInsight.Services.CSharp.CodeCleanup;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Impl.PsiManagerImpl;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;

namespace Neovolve.Extensibility.ReSharper
{
    /// <summary>
    /// The <see cref="ValueTypeAliasFormatter"/>
    /// class is used to format value types between alias and CLR type equivalents.
    /// </summary>
    [CodeCleanupModule(ModulesBefore = new[]
                                           {
                                               typeof(ReformatCode)
                                           })]
    [CLSCompliant(false)]
    public class ValueTypeAliasFormatter : ICodeCleanupModule
    {
        /// <summary>
        /// Stores the format option descriptor.
        /// </summary>
        private static readonly ValueTypeAliasFormatterDescriptor InternalDescriptor =
            new ValueTypeAliasFormatterDescriptor();

        /// <summary>
        /// Check if this module can handle given project file
        /// </summary>
        /// <param name="projectFile">The project file.</param>
        /// <returns><c>true</c> if the project file is available; otherwise <c>false</c>.</returns>
        public Boolean IsAvailable(IProjectFile projectFile)
        {
            // Get the language of the file
            PsiLanguageType languageType = ProjectFileLanguageServiceManager.Instance.GetPsiLanguageType(projectFile);

            return languageType == CSharpLanguageService.CSHARP;
        }

        /// <summary>
        /// Process clean-up on file. if <paramref name="range"/> is <see cref="F:JetBrains.DocumentModel.DocumentRange.InvalidRange"/>, then process the whole file
        /// </summary>
        /// <param name="projectFile">The project file.</param>
        /// <param name="range">The range of text selected.</param>
        /// <param name="profile">The code cleanup profile</param>
        /// <param name="canIncrementalUpdate">Defines whether incremental updates can be used.</param>
        /// <param name="progressIndicator">The progress indicator.</param>
        public void Process(
            IProjectFile projectFile,
            IPsiRangeMarker range,
            CodeCleanupProfile profile,
            out Boolean canIncrementalUpdate,
            IProgressIndicator progressIndicator)
        {
            canIncrementalUpdate = true;

            // Exit if there isn't a project file
            if (projectFile == null)
            {
                return;
            }

            // Get the formatting option
            TypeAliasFormat options = profile.GetSetting(InternalDescriptor);

            // Exit if the formatting option is to ignore
            if (options == TypeAliasFormat.Ignore)
            {
                return;
            }

            // Exit if the project file is available
            if (!IsAvailable(projectFile))
            {
                return;
            }

            ISolution solution = projectFile.GetSolution();

            PsiManagerImpl psiManager = PsiManagerImpl.GetInstance(solution);

            ICSharpFile file = psiManager.GetPsiFile(projectFile) as ICSharpFile;

            // Return if the file wasn't found
            if (file == null)
            {
                return;
            }

            if (range == null
                || range.IsValid == false)
            {
                ResolveHelper.ResolveNonqualifiedReferences(file);
            }

            Collection<ITreeNode> nodesToChange = NodeProcessor.ResolveFormattableNodes(
                file.ToTreeNode(), range, options, psiManager);

            if (nodesToChange.Count > 0)
            {
                // Loop through each node that can be changed
                for (Int32 index = 0; index < nodesToChange.Count; index++)
                {
                    ITreeNode node = nodesToChange[index];

                    // Update the node type usage
                    NodeProcessor.UpdateTypeUsage(node, options);
                }

                if (options == TypeAliasFormat.ConvertFromAlias)
                {
                    // Check that the file has the System namespace imported
                    NodeProcessor.EnsureNamespaceExists(file, "System");
                }
            }
        }

        /// <summary>
        /// Get default setting for given profile type
        /// </summary>
        /// <param name="profile">The code cleanup profile.</param>
        /// <param name="profileType">The code cleanup profile type.</param>
        public void SetDefaultSetting(CodeCleanupProfile profile, CodeCleanup.DefaultProfileType profileType)
        {
            profile.SetSetting(InternalDescriptor, TypeAliasFormat.Ignore);
        }

        /// <summary>
        /// Gets the collection of option descriptors
        /// </summary>
        /// <value>The collection of option descriptors.</value>
        public ICollection<CodeCleanupOptionDescriptor> Descriptors
        {
            get
            {
                return new CodeCleanupOptionDescriptor[]
                           {
                               InternalDescriptor
                           };
            }
        }

        /// <summary>
        /// Gets a value indicating whether this module is available on selection, or on the whole file
        /// </summary>
        /// <value><c>true</c> if this module is available on the selection; otherwise <c>false</c>.</value>
        public Boolean IsAvailableOnSelection
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets the language this module can operate with.
        /// </summary>
        /// <value>The language this module can operate with.</value>
        public PsiLanguageType LanguageType
        {
            get
            {
                return CSharpLanguageService.CSHARP;
            }
        }
    }
}